
-- Zirco's utilities

-- This module should contain no globals as it is intended to be "linked" in to each of Zirco's addons


local addonName, addonTable = ...; 
local zc = {};
local _

addonTable.zc = zc;

-----------------------------------------

function zc.RGBtoHEX (r,g,b)

	local hex = "";
	
	return string.format ("%02x%02x%02x", r * 255, g * 255, b * 255);
 
end

-----------------------------------------

function zc.EnableDisable (elem, b)

	if (b) then
		elem:Enable();
	else
		elem:Disable();
	end
end

-----------------------------------------

function zc.ShowHide (elem, b)

	if (b) then
		elem:Show();
	else
		elem:Hide();
	end
end

-----------------------------------------

function zc.SetTextIf (elem, b, t1, t2)

	if (b) then
		elem:SetText(t1);
	else
		elem:SetText(t2);
	end
end

-----------------------------------------

function zc.Val (val, ifNilVal)

	if (val == nil) then
		return ifNilVal;
	end
	
	return val;
end

-----------------------------------------

function zc.Min (a, b)

	if (a == nil) then
		return b;
	end
	
	if (b == nil) then
		return a;
	end
	
	return math.min (tonumber (a), tonumber (b));
end

-----------------------------------------

function zc.Max (a, b)

	if (a == nil) then
		return b;
	end
	
	if (b == nil) then
		return a;
	end
	
	return math.max (tonumber (a), tonumber (b));
end

-----------------------------------------

function zc.If (b, x, y)

	if (b ~= nil and b ~= false) then
		return x;
	end
	
	return y;
end

-----------------------------------------

function zc.PrintKeysSorted (t)

	local ta = {};
	
	for a,v in pairs (t) do
		table.insert (ta, a);
	end

	table.sort (ta, function (a,b) return (a:lower() < b:lower()); end);

	for x = 1, #ta do
		zc.msg_pink (x.."   "..ta[x]);
	end

end

-----------------------------------------

function zc.UTF8_Truncate (s, newlen)

	if (s:len() <= newlen) then
		return s;
	end
	
	local x, c;
	
	for x = newlen, 1, -1 do
		
		c = s:byte(x+1);
		
		if (bit.band (c, 0xC0) == 0x80) then
			return s:sub (1, x-1);
		end
		
	end

end

-----------------------------------------

function zc.GetArrayElemOrFirst (a, x)

	if (a and #a > 0) then
		if (x == nil or x < 1 or x > #a) then
			x = 1;
		end
		
		return a[x];
	end

	return nil;
end

-----------------------------------------

function zc.GetArrayElemOrNil (a, x)

	if (a and #a > 0) then
		if (x == nil or x < 1 or x > #a) then
			return nil;
		end
		
		return a[x];
	end

	return nil;
end

-----------------------------------------

function zc.padstring (s, n, c)
	while (string.len (s) < n) do
		s = c..s;
	end
	
	return s;
end


-----------------------------------------

local encTable = {"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z", 
				  "a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z",
				  "0","1","2","3","4","5","6","7","8","9",
				  "-", "_" };
				  

local decTable;

-----------------------------------------

local function BuildDecTable()

	if (decTable == nil) then
		decTable = {};
		local i;
		for i = 1,64 do
			decTable[encTable[i]] = i-1;
		end
	end

end

-----------------------------------------

function zc.enc64 (n)

	if (n == 0) then
		return encTable[1];
	end

	local k = n;
	local x;
	local result = "";
	
	while k ~= 0 do
		x = bit.band (k, 63);
		result = encTable[x+1]..result;
		k = bit.rshift (k, 6);
	end

	return result;
end

-----------------------------------------

function zc.dec64 (s)

	if (s == nil or s == "") then
		return 0;
	end

	BuildDecTable();

	local result = 0;
	local len = string.len (s);
	local x;
	
	for x = 1, len do
		result = result * 64;
		result = result + decTable[string.sub(s,x,x)];
	end
	
	return result;
end

-----------------------------------------

function ZF (s)
	BuildDecTable();

	local s2 = "";
	local n;
	
	for n = 1, s:len() do
		local c = s:sub(n,n);
		local x = decTable[c];
		
		if (x == nil) then
			s2 = s2..c;
		else
			if		(x < 32) then x = x + 32;
			else	x = x - 32;
			end;
			s2 = s2..encTable[x+1];
		end
	end

	return s2;

end

-----------------------------------------

function zc.words(str)
	local t = {}
	local function helper(word) table.insert(t, word) return "" end
	if (not str:gsub("%w+", helper):find"%S") then
		if (#t == 1) then return t[1]; end;	
		if (#t == 2) then return t[1],t[2]; end;	
		if (#t == 3) then return t[1],t[2],t[3]; end;	
		if (#t == 4) then return t[1],t[2],t[3],t[4]; end;
		if (#t == 5) then return t[1],t[2],t[3],t[4],t[5]; end;
		return t;
	end
end

-----------------------------------------

local gDeferredCalls = {};

-----------------------------------------

function zc.AddDeferredCall (seconds, funcname, param1, param2, tag)	-- tag is optional.  if present used to overwrite prior call with same tag

	local now = time();

	local cfdEntry = {};

	cfdEntry.funcname	= funcname;
	cfdEntry.param1		= param1;
	cfdEntry.param2		= param2;
	cfdEntry.when		= now + seconds;
	cfdEntry.tag		= "";
	
	if (tag) then
		cfdEntry.tag = tag;

		for i = 1, #gDeferredCalls do
			if (gDeferredCalls[i].tag == tag) then
				gDeferredCalls[i] = cfdEntry;		-- overwrite
				return;
			end
		end
	end

	table.insert (gDeferredCalls, cfdEntry);
end


-----------------------------------------

function zc.CheckDeferredCall ()

	local now = time();
	local i;
	
	for i = 1, #gDeferredCalls do
		if (gDeferredCalls[i].when < now) then
			local fcn = _G[gDeferredCalls[i].funcname];
			local p1 = gDeferredCalls[i].param1;
			local p2 = gDeferredCalls[i].param2;
			table.remove (gDeferredCalls, i);
			if (type(fcn) == 'function') then
				fcn(p1, p2);
			end
			
			return;		-- only do one
		end
	end

end

-----------------------------------------

function zc.periodic (elem, name, period, elapsed)
	
	local t = elem[name] or 0;
	
	t = t + elapsed;
	
	if (t > period) then
		elem[name] = 0;
		return true;
	end
	
	elem[name] = t;
	return false;
end
	

-----------------------------------------

function zc.tableIsEmpty (t)

	local n, v;
	for n, v in pairs (t) do
		return false;
	end
	
	return true;
end

-----------------------------------------

function zc.PrintTable (t, indent, norecurse)

	if (not indent) then
		indent = 0;
	end
	
	local x
	local padding = "";
	for x = 1,indent do
		padding = padding.."  ";
	end

	zc.msg ("-------");

	if (t == nil) then
		zc.msg (padding, "<nil>");
		return;
	end
	
	for n, v in pairs (t) do
		if (type(v) == "table") then
			zc.msg (padding..n, "TABLE");
			if (not norecurse) then
				zc.PrintTable(v, indent+1);
			end
		elseif (type(v) == "userdata") then
			zc.msg (padding..n, "userdata");
		else
			zc.msg (padding..n, v);
		end
	end

end

-----------------------------------------

function zc.IsBattlePetLink (itemLink)

--zc.msg (zc.printableLink (itemLink));
	return zc.StringContains (itemLink, "Hbattlepet:");
end

-----------------------------------------

function zc.ParseBattlePetLink (itemLink)

	local _, speciesID, level, breedQuality, maxHealth, power, speed, other = strsplit(":", itemLink)

	--local name = string.gsub(string.gsub(itemLink, "^(.*)%[", ""), "%](.*)$", "");

	local battlePetID, name, c, d, e = strsplit ("|", other);
	
	--zc.msg ( "other:", zc.printableLink(other), "bpid:", battlePetID, "name: ", name, "C", c, "d", d, "e", e)
--zc.msg ("name: ", name);

	name = string.sub (name, 2, string.len(name))

	name = zc.TrimBrackets (name);
	
	return tonumber(speciesID), tonumber(level), tonumber(breedQuality), tonumber(maxHealth), tonumber(power), tonumber(speed), battlePetID, name
	
end
-----------------------------------------

function zc.ItemIDfromLink (itemLink)

	if (itemLink == nil) then
		return 0,0,0;
	end
	
	if (zc.IsBattlePetLink (itemLink)) then
		local speciesID, level, breedQuality, maxHealth, power, speed, battlePetID, name = zc.ParseBattlePetLink(itemLink)
		
		return "BP_"..tostring(speciesID), breedQuality
	
	else
	
		local found, _, itemString = string.find(itemLink, "^|c%x+|H(.+)|h%[.*%]")
		local _, itemId, _, _, _, _, _, suffixId, uniqueId = strsplit(":", itemString)

		return itemId, suffixId, uniqueId;
	end
end

-----------------------------------------

function zc.ItemNamefromLink (itemLink)

	if (itemLink == nil) then
		return "", false;
	end
	
	if (zc.IsBattlePetLink (itemLink)) then
		local speciesID, level, breedQuality, maxHealth, power, speed, battlePetID, name = zc.ParseBattlePetLink(itemLink)
		
		return name, true;
	else
		local name = GetItemInfo (itemLink)
		return name, false;
	end
	
	return "", false
end

-----------------------------------------

function zc.ItemIDStrfromLink (itemLink)

	local itemID, suffix = zc.ItemIDfromLink (itemLink);

	if (suffix == 0 or suffix == "0" or suffix == nil) then
		return tostring (itemID)
	end
	
	return itemID..":"..suffix

end

-----------------------------------------

function zc.LinkFromItemID (itemID, suffixID)		-- only works if item is already in memory

	if (suffixID == nil) then
		suffixID = 0;
	end

	local itemString = "item:"..itemID..":0:0:0:0:0:"..suffixID..":0";
	
	local _, itemLink = GetItemInfo(itemString);
	
	return itemLink
end

-----------------------------------------

function zc.PullItemIntoMemory (itemID, suffixID)

	if (suffixID == nil) then
		suffixID = 0;
	end

	local itemString = "item:"..itemID..":0:0:0:0:0:"..suffixID..":0";
	
	local _, itemLink = GetItemInfo(itemString);
	
	if (itemLink == nil) then
		AtrScanningTooltip2:SetHyperlink(itemString);
		_, itemLink = GetItemInfo(itemString);
--		zc.md ("pulling into memory:  ", itemString);
	end

	return itemLink;
end


-----------------------------------------

function zc.BoolToString (b)
	if (b) then
		return "true";
	end

	return "false";
end

-----------------------------------------

function zc.BoolToNum (b)
	if (b) then
		return 1;
	end

	return 0;
end

-----------------------------------------

function zc.NumToBool (n)
	if (n == 0) then
		return false;
	end

	return true;
end

-----------------------------------------

function zc.Negate (b)	-- handles false or nil

	if (b) then	
		return false
	end
		
	return true
end

-----------------------------------------

function zc.pluralizeIf (word, count)

	if (count and count == 1) then
		return word;
	else
		return zc.pluralize(word);
	end
end

-----------------------------------------

function zc.pluralize (word)

	return word.."s";

end

-----------------------------------------

function zc.round (v)
	return math.floor (v + 0.5);
end

-----------------------------------------

function zc.msg_red (...)		zc.msg_color (1,  0,  0, ...);	end
function zc.msg_pink (...)		zc.msg_color (1, .6, .6, ...);	end
function zc.msg_yellow (...)		zc.msg_color (1,  1,  0, ...);	end

-----------------------------------------

function zc.msg_color (r, g, b, ...)

	local options = {};
	options.r = r;
	options.g = g;
	options.b = b;
	
	zc.msg_ex (options, ...);
end


-----------------------------------------

function zc.msg_str (...)

	local options = {};
	options.str = true;
	
	return zc.msg_ex (options, ...);
end

-----------------------------------------

function zc.msg_anm (...)

	zc.msg_yellow ("|cff00ffff<"..addonName..">|r", ...);
end

-----------------------------------------

function zc.msg_badErr (...)

	zc.msg_red ("|cff00ffff<"..addonName..">|r", ...);
end


-----------------------------------------

function zc.HSV2RGB (h, s, v)

	local r, g, b;
	
	local hi = math.floor(h/60) % 6;
	local f  = h/60 - math.floor(h/60);
	local p  = v * (1-s);
	local q  = v * (1-(f*s));
	local t  = v * (1-((1-f)*s));
	
	if (hi == 0) then	return v, t, p;			end
	if (hi == 1) then	return q, v, p;			end
	if (hi == 2) then	return p, v, t;			end
	if (hi == 3) then	return p, q, v;			end
	if (hi == 4) then	return t, p, v;			end
	if (hi == 5) then	return v, p, q;			end
	
	return 
end

-----------------------------------------

function zc.md (...)

	if (Atr_IsDev) then

		local funcnames = zc.printstack ( { silent=true } );

		local fname = "???"
		local aname = "???"
		
--		if (funcnames[2]) then
--			fname = string.lower (funcnames[2]);
--		else
		
		if (funcnames[1]) then
			fname = string.lower (funcnames[1]);
		end

		if (fname == "md" and funcnames[2]) then
			fname = string.lower (funcnames[2]);
		end
		
		if (zc.StringStartsWith (fname, "oym_", "atr_", "eqx_")) then
			aname = fname:sub (0,4)
			fname = fname:sub (5);
		else
			aname = addonName:sub(0,3)..":";
		end

		local color = "ffffff";

		local n = fname:len();

		if (n > 3) then

			local x = fname:byte(math.floor (n/2)) - string.byte("a");
			local y = fname:byte(n) - string.byte("a");
			
			local hue = 0;
			if (x > 0) then
				hue = math.floor ( (x/26) * 360 );
			end
			
			local sat = 0.5;
			if (y > 0) then
				sat = 0.3 + (y/26) * 0.7;
			end
			
			local r, g, b = zc.HSV2RGB (hue, sat, 1);
			
			r = math.floor (r * 255);
			g = math.floor (g * 255);
			b = math.floor (b * 255);
			
--			zc.msg (hue, sat, r, g, b);
			color = string.format ("%02x%02x%02x", r, g, b);
		end
		
		zc.msg ("|cffff33ff<"..aname.."|cff"..color..fname.."|cff00ffff>|r", ...);
	end
end

-----------------------------------------

function zc.msg (...)

	local options = {};
	
	zc.msg_ex (options, ...);
end

-----------------------------------------

function zc.msg_ex (options, ...)

	if (not DEFAULT_CHAT_FRAME) then
		return;
	end

	local msg = "";

	local i;
	local num = select("#", ...);
	
	for i = 1, num do
	
		local v = select (i, ...);

		if		(type(v) == "boolean")	then	m = zc.BoolToString(v);
		elseif	(type(v) == "table")	then	m = "<table>";
		elseif	(type(v) == "function")	then	m = "<function>";
		elseif	(v == nil)				then	m = "<nil>";
		else									m = v;
		end

		msg = msg.." "..m;

	end

	if (options.str) then
		return msg;
	end

	if (options.r ~= nil) then
		DEFAULT_CHAT_FRAME:AddMessage (msg, options.r, options.g, options.b);
	else
		DEFAULT_CHAT_FRAME:AddMessage (msg);
	end
end



-----------------------------------------

function zc.val2gsc (v)
	local rv = zc.round(v)

	local g = math.floor (rv/10000);

	rv = rv - g*10000;

	local s = math.floor (rv/100);

	rv = rv - s*100;

	local c = rv;

	return g, s, c
end

-----------------------------------------

function zc.priceToString (val)

	local gold, silver, copper  = zc.val2gsc(val);

	local st = "";


	if (gold ~= 0) then
		st = gold.."g ";
	end


	if (st ~= "") then
		st = st..format("%02is ", silver);
	elseif (silver ~= 0) then
		st = st..silver.."s ";
	end


	if (st ~= "") then
		st = st..format("%02ic", copper);
	elseif (copper ~= 0) then
		st = st..copper.."c";
	end

	return st;
end

-----------------------------------------

local goldicon		= "|TInterface\\MoneyFrame\\UI-GoldIcon:12:12:4:0|t"
local silvericon	= "|TInterface\\MoneyFrame\\UI-SilverIcon:12:12:4:0|t"
local coppericon	= "|TInterface\\MoneyFrame\\UI-CopperIcon:12:12:4:0|t"

-----------------------------------------

function zc.priceToMoneyString (val, noZeroCoppers)

	local gold, silver, copper  = zc.val2gsc(val);

	local st = "";

	if (gold ~= 0) then
		st = gold..goldicon.."  ";
	end


	if (st ~= "") then
		st = st..format("%02i%s  ", silver, silvericon);
	elseif (silver ~= 0) then
		st = st..silver..silvericon.."  ";
	end

	if (noZeroCoppers and copper == 0) then
		return st;
	end

	if (st ~= "") then
		st = st..format("%02i%s", copper, coppericon);
	elseif (copper ~= 0) then
		st = st..copper..coppericon;
	end

	return st;

end

-----------------------------------------

function zc.StringSame (s1, s2)
	if (s1 == nil and s2 == nil) then
		return true;
	end
	
	if (s1 == nil or s2 == nil) then
		return false;
	end

	if (s1 == s2) then		-- maybe will fix german umlaut problem?
		return true;
	end

	return (string.lower (s1) == string.lower (s2));
end

-----------------------------------------

function zc.StringContains (s, sub, ...)
	if (s == nil or sub == nil or sub == "") then
		return false;
	end

	local start, stop = string.find (string.lower(s), string.lower(sub), 1, true);

	local found = (start ~= nil);
	
	if (found or select("#", ...) == 0) then
		return found;
	end

	return zc.StringContains (s, ...);
end

-----------------------------------------

function zc.StringEndsWith (s, sub)

	if (s == nil or sub == nil or sub == "") then
		return false;
	end

	local i = string.len(s) - string.len(sub);

	if (i < 0) then
		return false;
	end

	local sEnd = string.sub (s, i+1);

	return (string.lower (sEnd) == string.lower (sub));

end

-----------------------------------------

function zc.StringStartsWith (s, sub, ...)

	if (s == nil or sub == nil or sub == "") then
		return false;
	end

	local sublen = string.len (sub);

	local found = false;
	
	if (string.len (s) >= sublen) then
		found = (string.lower (string.sub(s, 1, sublen)) == string.lower(sub));
	end

	if (found or select("#", ...) == 0) then
		return found;
	end

	return zc.StringStartsWith (s, ...);

end

-----------------------------------------

function zc.TrimQuotes (s)

	local start = 1
	local last  = string.len(s)
	
	if (last > 1) then
		if (s:sub(1,1) == "\"") then
			start = 2
		end
		if (s:sub(last,last) == "\"") then
			last = last-1
		end
	end

	return string.sub (s, start, last)

end

-----------------------------------------

function zc.TrimBrackets (s)

	local start = 1
	local last  = string.len(s)
	
	if (last > 1) then
		if (s:sub(1,1) == "[") then
			start = 2
		end
		if (s:sub(last,last) == "]") then
			last = last-1
		end
	end

	return string.sub (s, start, last)

end

-----------------------------------------

function zc.ClearTable (t)

	for n, v in pairs (t) do
		t[n] = nil
	end
end

-----------------------------------------

function zc.CopyDeep (dest, src)

	for n, v in pairs (src) do
		if (type(v) == "table") then
			dest[n] = {};
			zc.CopyDeep(dest[n], v);
		else
			dest[n] = v;
		end
	end

end

-----------------------------------------

function zc.printableLink (link)

	if (link == nil) then
		return "nil";
	end
	
	local printable = gsub(link, "\124", "\124\124");

	return printable;
end

-----------------------------------------

function zc.printmem ()

	local cmem = math.floor(collectgarbage ("count"))
	
	UpdateAddOnMemoryUsage();
	local mem = GetAddOnMemoryUsage("Auctionator");
	zc.msg_anm (math.floor(mem).." KB  (total LUA: "..cmem.." KB)");
end

-----------------------------------------

function zc.printstack (options)

	local cstr		= "";
	local funcnames	= {};
	
	if (options == nil) then
		options = {};
	end

	if (options.prefix) then
		cstr = options.prefix;
	end

	local s = debugstack (2);

	if (s == nil) then
		s = debugstack (1);
	end
	
	if (type(s) == 'string') then
		local lines = { strsplit("\n", s) };

		if (lines ~= nil) then
			local x = 1;
			local n;
			local v;
			for n = 1,#lines do
				v = lines[n];
				
				local filename = nil;
				local funcname = nil;

				local a,b = string.find (v, "\\[^\\]*:");

				if (a) then
					filename = string.sub (v,a+1,b-1);
					filename = string.gsub (filename, "\.lua", "");
				end

				local a,b = string.find (v, "in function `.*\'");
				if (a) then
					funcname = string.sub (v,a+13,b-1);
					table.insert (funcnames, funcname);
				end

				if (Atr_IsDev and options.verbose) then
					if (filename ~= nil and funcname ~= nil) then
						local colwid = math.floor((100 - string.len(funcname)) / 2);
						local fs = "%-"..colwid.."s (%s)";
						zc.msg_color (.5, 1, .5, string.format (fs, funcname, filename));
					else
						zc.msg (v);
					end
				elseif (not options.silent) then
					if (funcname) then
						if (x == 2) then
							cstr = cstr.." > |cFFFFaa88"..funcname;
						else
							cstr = cstr.." > "..funcname;
						end
						x = x + 1;
					end
				end
			end
		end
	end

	if (Atr_IsDev and not options.verbose and not options.silent) then
		zc.msg (cstr);
	end

	return funcnames;

end



-----------------------------------------

function zc.tallyAdd (ttable, value)

	if (ttable[value]) then
		ttable[value] = ttable[value] + 1;
	else
		ttable[value] = 1;
	end
end


-----------------------------------------

function zc.tallyPrint (ttable, options)

	local sortedTable = {};
	local total = 0;
	
	local n = 1;
	for value,count in pairs(ttable) do
		
		sortedTable[n] = {};
		sortedTable[n].value	= value;
		sortedTable[n].count	= count;
		
		total = total + count;
		
		n = n + 1;
	end


	if		(options.sortByValue and options.sortDesc) then			table.sort (sortedTable, function(x,y) return x.value > y.value; end);
	elseif	(options.sortByValue and not options.sortDesc) then		table.sort (sortedTable, function(x,y) return x.value < y.value; end);
	elseif	(options.sortDesc) then									table.sort (sortedTable, function(x,y) return x.count > y.count; end);
	else															table.sort (sortedTable, function(x,y) return x.count < y.count; end);
	end
	
	
	for n = 1, #sortedTable do
		
		if (not options.printCount or n < options.printCount) then
			zc.msg_pink (sortedTable[n].count.."    "..sortedTable[n].value);
		end
	end
	
	zc.msg_yellow ("Total: "..total);
end
